/*
 * Created on Feb 13, 2005
 * Created by dfhuynh
 */
package edu.mit.simile.longwell.query.engine;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.apache.log4j.Logger;

import com.hp.hpl.jena.rdf.model.Model;

import edu.mit.simile.longwell.Profile;
import edu.mit.simile.longwell.StructuredModelBase;
import edu.mit.simile.longwell.query.IAnswer;
import edu.mit.simile.longwell.query.IQueryModel;
import edu.mit.simile.longwell.query.Order;
import edu.mit.simile.longwell.query.Query;
import edu.mit.simile.longwell.query.QueryException;
import edu.mit.simile.longwell.query.Ranking;
import edu.mit.simile.longwell.query.Restriction;
import edu.mit.simile.longwell.query.bucket.BucketerManager;
import edu.mit.simile.longwell.query.compare.ComparatorManager;
import edu.mit.simile.longwell.query.compare.IComparator;
import edu.mit.simile.longwell.query.project.IProjector;
import edu.mit.simile.longwell.query.project.ProjectorManager;

/**
 * @author dfhuynh
 */
public class QueryEngine extends StructuredModelBase implements IQueryModel {
	final static private Logger s_logger = Logger.getLogger(QueryEngine.class);
	
	final protected Profile 	m_profile;
	
	final protected Refiner		m_refiner;
	
	final protected Narrower	m_narrower;
	final protected Broadener	m_broadener;
	final protected Relator		m_relator;
	
	final protected Sorter		m_sorter;
	final protected Indexer		m_indexer;
	final protected Paginator	m_paginator;
	
	final protected ProjectorManager	m_projectorManager;
	final protected BucketerManager		m_bucketerManager;
	final protected ComparatorManager	m_comparatorManager;
	
	public class Answer implements IAnswer {
		final protected Query 	m_query;
		final protected String	m_locale;
		final protected boolean	m_fresh;
		
		final protected SortedSet	m_restrictors;
		
		protected Set		m_objects;
		protected List		m_sortedObjects;
		
		protected List		m_pages;
		protected List		m_index;
		
		protected List		m_orders;
		protected List		m_orderInfos;
		protected List		m_rankings;
		
		protected List		m_narrowingFacets;
		protected Set		m_broadeningFacets;
		protected Set		m_relatingQueries;
		
		protected Answer(Query query, boolean fresh) throws QueryException {
			m_query = query;
			m_locale = getLocale(query);
			m_fresh = fresh;
			m_restrictors = makeRestrictors(query.getRestrictions(), m_locale);
		}
		
		/* (non-Javadoc)
		 * @see edu.mit.simile.longwell.query.IAnswer#getObjects()
		 */
		public Set getObjects() throws QueryException {
			//if (m_objects == null) {
				long start = System.currentTimeMillis();
				
				m_objects = m_refiner.refine(m_restrictors, m_locale, m_fresh);
				
				s_logger.info("Refined in " + 
					(System.currentTimeMillis() - start) + "ms");
			//}
			return m_objects;
		}

		/* (non-Javadoc)
		 * @see edu.mit.simile.longwell.query.IAnswer#getSortedObjects()
		 */
		public List getSortedObjects() throws QueryException {
			if (m_sortedObjects == null) {
				Set objects = getObjects();
				
				getOrderInfos();
				
				long start = System.currentTimeMillis();
				
				m_sortedObjects = m_sorter.sort(objects, m_orders, m_locale, m_fresh);
				
				s_logger.info("Sorted " + objects.size() + " items in " + 
					(System.currentTimeMillis() - start) + "ms");
			}
			return m_sortedObjects;
		}

		/* (non-Javadoc)
		 * @see edu.mit.simile.longwell.query.IAnswer#getPages()
		 */
		public List getPages() throws QueryException {
			if (m_pages == null) {
				List sortedObjects = getSortedObjects();
				
				long start = System.currentTimeMillis();
				
				m_pages = m_paginator.paginate(sortedObjects, m_query, 10, m_fresh);
				
				s_logger.info("Paginated " + sortedObjects.size() + " items in " + 
					(System.currentTimeMillis() - start) + "ms");
			}
			return m_pages;
		}

		/* (non-Javadoc)
		 * @see edu.mit.simile.longwell.query.IAnswer#getIndex()
		 */
		public List getIndex() throws QueryException {
			if (m_index == null) {
				Set objects = getObjects();
				List sortedObjects = getSortedObjects();
				
				long start = System.currentTimeMillis();
				
				m_index = m_indexer.index(
					objects, sortedObjects, m_orders, 10, m_locale, m_fresh);
				
				s_logger.info("Indexed " + sortedObjects.size() + " items in " + 
					(System.currentTimeMillis() - start) + "ms");
			}
			return m_index;
		}

		/* (non-Javadoc)
		 * @see edu.mit.simile.longwell.query.IAnswer#getOrderInfos()
		 */
		public List getOrderInfos() throws QueryException {
			if (m_orderInfos == null) {
				List rankings = getRankings();
				
				long start = System.currentTimeMillis();
				
				m_orders = m_query.getOrders();
				m_orderInfos = new ArrayList();
				
				if (m_orders.size() == 0) {
					if (rankings.size() > 0) {
						m_orders = new ArrayList();
						
						Ranking ranking = (Ranking) rankings.get(0);
						
						m_orders.add(new Order(
							ranking.m_projector.getClass().getName(),
							ranking.m_projector.getParameter(),
							ranking.m_comparator.getClass().getName(),
							ranking.m_comparator.getParameter(),
							ranking.m_comparator.isAscendingByDefault(m_locale)
							));
						m_orderInfos.add(new OrderInfo(
							ranking.m_projector.getLabel(m_locale),
							ranking.m_comparator.isAscendingByDefault(m_locale) ?
								ranking.m_comparator.getAscendingLabel(m_locale) :
								ranking.m_comparator.getDescendingLabel(m_locale)
							));
						
						if (rankings.size() > 1) {
							ranking = (Ranking) rankings.get(1);
							
							m_orders.add(new Order(
								ranking.m_projector.getClass().getName(),
								ranking.m_projector.getParameter(),
								ranking.m_comparator.getClass().getName(),
								ranking.m_comparator.getParameter(),
								ranking.m_comparator.isAscendingByDefault(m_locale)
								));
							m_orderInfos.add(new OrderInfo(
								ranking.m_projector.getLabel(m_locale),
								ranking.m_comparator.isAscendingByDefault(m_locale) ?
									ranking.m_comparator.getAscendingLabel(m_locale) :
									ranking.m_comparator.getDescendingLabel(m_locale)
								));
						}
					}
				} else {
					Iterator i = m_orders.iterator();
					while (i.hasNext()) {
						Order order = (Order) i.next();
						
						IProjector projector = (IProjector) 
							m_projectorManager.getProjector(
								order.m_projectorName, 
								order.m_projectorParameter, 
								m_locale);
						IComparator comparator = (IComparator)
							m_comparatorManager.getComparator(
								order.m_comparatorName,
								order.m_comparatorParameter);
						
						m_orderInfos.add(new OrderInfo(
							projector.getLabel(m_locale),
							order.m_ascending ?
								comparator.getAscendingLabel(m_locale) :
								comparator.getDescendingLabel(m_locale)
							));
					}
				}
				
				s_logger.info("Got order infos in " + 
					(System.currentTimeMillis() - start) + "ms");
			}
			return m_orderInfos;
		}

		/* (non-Javadoc)
		 * @see edu.mit.simile.longwell.query.IAnswer#getRankings()
		 */
		public List getRankings() throws QueryException {
			if (m_rankings == null) {
				Set objects = getObjects();
				
				long start = System.currentTimeMillis();
				
				m_rankings = m_sorter.suggestOrders(objects, m_restrictors, m_locale, m_fresh);
				
				s_logger.info("Suggested orders for " + objects.size() + " items in " + 
					(System.currentTimeMillis() - start) + "ms");
			}
			return m_rankings;
		}

		/* (non-Javadoc)
		 * @see edu.mit.simile.longwell.query.IAnswer#getNarrowingFacets()
		 */
		public List getNarrowingFacets() throws QueryException {
			s_logger.info("QUERY ENGINE --> getNarrowingFacets");
			if (m_narrowingFacets == null) {
				Set objects = getObjects();
				
				long start = System.currentTimeMillis();
				
				m_narrowingFacets = m_narrower.narrow(
					objects, m_query.getExplicitFacets(), m_locale, m_fresh);
				
				s_logger.info("Narrowed " + objects.size() + " items in " + 
					(System.currentTimeMillis() - start) + "ms");
			}
			return m_narrowingFacets;
		}

		/* (non-Javadoc)
		 * @see edu.mit.simile.longwell.query.IAnswer#getBroadeningFacets()
		 */
		public Set getBroadeningFacets() throws QueryException {
			if (m_broadeningFacets == null) {
				Set objects = getObjects();
				
				long start = System.currentTimeMillis();
				
				m_broadeningFacets = m_broadener.broaden(
					objects, m_restrictors, m_locale, m_fresh);
				
				s_logger.info("Broadened " + objects.size() + " items in " + 
					(System.currentTimeMillis() - start) + "ms");
			}
			return m_broadeningFacets;
		}

		/* (non-Javadoc)
		 * @see edu.mit.simile.longwell.query.IAnswer#getRelatingQueries()
		 */
		public Set getRelatingQueries() throws QueryException {
			if (m_relatingQueries == null) {
				Set objects = getObjects();
				
				long start = System.currentTimeMillis();
				
				m_relatingQueries = m_relator.relate(objects, m_locale, m_fresh);

				s_logger.info("Related " + objects.size() + " items in " + 
					(System.currentTimeMillis() - start) + "ms");
			}
			return m_relatingQueries;
		}
		
	}
	
	/**
	 * @param modelURI
	 * @param profile
	 * @param dir
	 */
	public QueryEngine(Profile profile, File dir) {
		super(profile, dir);
		m_profile = profile;
		
		m_refiner = new Refiner(m_profile, this);
		m_narrower = new Narrower(m_profile, this);
		m_broadener = new Broadener(m_profile, this);
		m_relator = new Relator(m_profile, this);
		m_sorter = new Sorter(m_profile, this);
		m_indexer = new Indexer(m_profile, this);
		m_paginator = new Paginator(m_profile, this);
		
		m_projectorManager = new ProjectorManager(m_profile);
		m_bucketerManager = new BucketerManager(m_profile);
		m_comparatorManager = new ComparatorManager(m_profile);
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.query.IQueryEngine#query(edu.mit.simile.longwell.query.Query, boolean)
	 */
	public IAnswer query(Query query, boolean fresh) throws QueryException {
		return new Answer(query, fresh);
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.query.IQueryEngine#queryObjects(edu.mit.simile.longwell.query.Query, boolean)
	 */
	public Set queryObjects(Query query, boolean fresh) throws QueryException {
		String 		locale = getLocale(query);
		SortedSet	restrictors = makeRestrictors(query.getRestrictions(), locale);
		
		return m_refiner.refine(restrictors, locale, fresh);
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.query.IQueryEngine#queryCount(edu.mit.simile.longwell.query.Query, boolean)
	 */
	public int queryCount(Query query, boolean fresh) throws QueryException {
		return queryObjects(query, fresh).size();
	}
	
	public ProjectorManager getProjectorManager() {
		return m_projectorManager;
	}
	
	public BucketerManager getBucketerManager() {
		return m_bucketerManager;
	}
	
	public ComparatorManager getComparatorManager() {
		return m_comparatorManager;
	}

	protected String getLocale(Query query) {
		return query.getFirstParamValue("locale");
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.IProfileListener#onBeforeAdd(com.hp.hpl.jena.rdf.model.Model)
	 */
	public void onBeforeAdd(Model model) {
		m_projectorManager.onBeforeAdd(model);
		m_comparatorManager.onBeforeAdd(model);
		m_bucketerManager.onBeforeAdd(model);
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.IProfileListener#onAfterAdd(com.hp.hpl.jena.rdf.model.Model)
	 */
	public void onAfterAdd(Model model) {
		m_projectorManager.onAfterAdd(model);
		m_comparatorManager.onAfterAdd(model);
		m_bucketerManager.onAfterAdd(model);
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.IProfileListener#onFailingAdd(com.hp.hpl.jena.rdf.model.Model)
	 */
	public void onFailingAdd(Model model) {
		m_projectorManager.onFailingAdd(model);
		m_comparatorManager.onFailingAdd(model);
		m_bucketerManager.onFailingAdd(model);
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.IProfileListener#onBeforeRemove(com.hp.hpl.jena.rdf.model.Model)
	 */
	public void onBeforeRemove(Model model) {
		m_projectorManager.onBeforeRemove(model);
		m_comparatorManager.onBeforeRemove(model);
		m_bucketerManager.onBeforeRemove(model);
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.IProfileListener#onAfterRemove(com.hp.hpl.jena.rdf.model.Model)
	 */
	public void onAfterRemove(Model model) {
		m_projectorManager.onAfterRemove(model);
		m_comparatorManager.onAfterRemove(model);
		m_bucketerManager.onAfterRemove(model);
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.IProfileListener#onFailingRemove(com.hp.hpl.jena.rdf.model.Model)
	 */
	public void onFailingRemove(Model model) {
		m_projectorManager.onFailingRemove(model);
		m_comparatorManager.onFailingRemove(model);
		m_bucketerManager.onFailingRemove(model);
	}
	
	protected SortedSet makeRestrictors(Collection restrictions, String locale) 
	throws QueryException {
		SortedSet restrictors = new TreeSet(new Comparator() {
			public boolean equals(Object obj) {
				return false;
			}

			public int compare(Object o1, Object o2) {
				Restrictor r1 = (Restrictor) o1;
				Restrictor r2 = (Restrictor) o2;
				
				int i = 0;
				if (r1.m_projector.isEfficientForRootProjection()) {
					if (!r2.m_projector.isEfficientForRootProjection()) {
						i = -1;
					}
				} else if (r2.m_projector.isEfficientForRootProjection()) {
					i = 1;
				}
				
				if (i == 0) {
					i = r1.m_projector.getClass().getName().compareTo(
							r2.m_projector.getClass().getName());
				}
				if (i == 0) {
					i = r1.m_projector.getParameter().compareTo(
							r2.m_projector.getParameter());
				}
				if (i == 0) {
					i = r1.m_bucketer.getClass().getName().compareTo(
							r2.m_bucketer.getClass().getName());
				}
				if (i == 0) {
					i = r1.m_bucketerParameter.compareTo(
							r2.m_bucketerParameter);
				}

				return i;
			}
		});
		
		Iterator i = restrictions.iterator();
		while (i.hasNext()) {
			Restriction restriction = (Restriction) i.next();
			
			restrictors.add(
				new Restrictor(
					m_projectorManager.getProjector(
						restriction.m_projectorName, 
						restriction.m_projectorParameter,
						locale),
					m_bucketerManager.getBucketer(
							restriction.m_bucketerName),
					restriction.m_bucketerParameter,
					restriction.m_id));
		}
		
		return restrictors;
	}
}
